#ifndef NIXONBOT_CORE_BNCS_PROTOCOL_STARTADVEXCLIENT_H_
#define NIXONBOT_CORE_BNCS_PROTOCOL_STARTADVEXCLIENT_H_

#include <nixonbot/core/bncs/protocol/Message.h>
#include <nixonbot/core/bncs/protocol/MessageFactory.h>

namespace nixonbot {
namespace core {
namespace bncs {
namespace protocol {

struct StartAdvExClientData : public QSharedData
{
  QString lobbyName;
  QString lobbyHost;
  quint32 lobbyVisibility;
  quint32 lobbyUptime;
  bool hasPassword;
  
  quint32 gameType;
  quint32 gameSpeed;
  quint32 gameVisibility;
  quint32 gameObservers;
  bool teamTogether;
  bool fixedTeamLocations;
  bool sharedUnits;
  bool randomHeroes;
  bool randomRaces;
  
  QString mapPath;
  quint16 mapWidth;
  quint16 mapHeight;
  quint32 mapCRC;
};

class StartAdvExClient : public Message
{
 public:
	static const int id = 0x1c;
	BNCS_MESSAGE_CLIENT_REGISTER(StartAdvExClient, id);

 public:
	enum LobbyVisibility
	{
	  LOBBYVIS_PUBLIC  = 0x00000010,
	  LOBBYVIS_PRIVATE = 0x00000011,
	  LOBBYVIS_NONE    = 0x00000012
	};

	enum GameType
	{
	  GAMETYPE_CUSTOM      = 0x00000001,
	  GAMETYPE_MELEE       = 0x00000002,
	  GAMETYPE_FFA         = 0x00000003,
	  GAMETYPE_ONEVSONE    = 0x00000004,
	  GAMETYPE_CTF         = 0x00000005,
	  GAMETYPE_GREED       = 0x00000006,
	  GAMETYPE_SLAUGHTER   = 0x00000007,
	  GAMETYPE_SUDDENDEATH = 0x00000008,
	  GAMETYPE_LADDER      = 0x00000009,
	  GAMETYPE_UMS         = 0x0000000A,
	  GAMETYPE_TEAMMELEE   = 0x0000000B,
	  GAMETYPE_TEAMFFA     = 0x0000000C,
	  GAMETYPE_TEAMCTF     = 0x0000000D,
	  GAMETYPE_E           = 0x0000000E,
	  GAMETYPE_TVB         = 0x0000000F,
	  GAMETYPE_IRONMAN     = 0x00000010
	};

	enum GameSpeed
	{
	  //MAPSPD mask 0x00000003
	  GAMESPEED_SLOW     = 0x00000000,
	  GAMESPEED_NORMAL   = 0x00000001,
	  GAMESPEED_FAST     = 0x00000002
	};

	enum GameVisibility
	{
	  //MAPVIS mask 0x00000F00
	  GAMEVIS_HIDE     = 0x00000100,
	  GAMEVIS_EXPLORED = 0x00000200,
	  GAMEVIS_NOFOG    = 0x00000400,
	  GAMEVIS_DEFAULT  = 0x00000800
	};

	enum GameObservers
	{
	  //MAPOBS mask 0x40003000
	  GAMEOBS_NONE     = 0x00000000,
	  GAMEOBS_DEFEAT   = 0x00002000,
	  GAMEOBS_EXTRA    = 0x00003000,
	  GAMEOBS_REFEREE  = 0x40000000
	};

 private:
	enum MiscOptions
	{
	  //Team/Unit/Hero/Race mask 0x07064000
	  TEAM_TOGETHER   = 0x00004000,
	  TEAM_FIXED      = 0x00060000,
	  UNIT_SHARE      = 0x01000000,
	  HERO_RANDOM     = 0x02000000,
	  RACE_RANDOM     = 0x04000000
	};

public:
	QSharedDataPointer<StartAdvExClientData> d;

public:
	StartAdvExClient()
	{
		d = new StartAdvExClientData();
	}
	
	virtual StartAdvExClient* create()
	{
		return new StartAdvExClient();
	}
	
	virtual quint32 id()
	{
		return id;
	}

	virtual int size()
	{
	  /* The +constants are for bytes that have unknown meaning. */
	  
	  int sslen = sizeof((quint32)d->flags) + 1 + sizeof(d->mapWidth) +
	    sizeof(d->mapHeight) + sizeof(d->mapCRC) + (d->mapPath.toAscii().size() + 1) +
	    (d->lobbyHost.toAscii().size() + 1) + 1;

	  return sizeof(d->lobbyVisibility) + sizeof(d->lobbyUptime) +
	    sizeof(d->gameType) + 4 + 4 + (d->lobbyName.toUtf8().size() + 1) +
	    sizeof(d->hasPassword) + 1 + 8 + (sslen + 1);
	}

protected:
	virtual void readFrom(QDataStream& ds)
	{
		int bo = ds.byteOrder();
		ds.setByteOrder(QDataStream::LittleEndian);
		
		/* Read packet. */
		quint8 hasPass;
		QByteArray ss;
		ds >> this->lobbyVisibility;
		ds >> this->lobbyUptime;
		ds >> this->gameType;
		ds.skipRawData(4); /* Unknown constant. */
		ds.skipRawData(4); /* Custom game constant. */
		this->lobbyName = QString::fromUtf8(readStringFrom(ds).constData());
		ds >> hasPass;
		this->hasPassword = hasPass;
		ds.skipRawData(1); /* Slots free. */
		ds.skipRawData(8); /* Host counter. */
		ss = readStringFrom(ds); /* Stat string. */

		/* Unpack stat string. */
		quint32 flags;
		ss = BncsMessage::decodeStatString(ss);
		QDataStream ss_stream(ss);
		ss_stream.setByteOrder(QDataStream::LittleEndian);
		ss_stream >> flags;
		ss_stream.skipRawData(1);
		ss_stream >> this->mapWidth;
		ss_stream >> this->mapHeight;
		ss_stream >> this->mapCRC;
		this->mapPath = readStringFrom(ss_stream);
		this->lobbyHost = readStringFrom(ss_stream);
		ss_stream.skipRawData(1);

		/* Unpack flags. */
		this->gameSpeed = flags & 0x00000003;
		this->gameVisibility = flags & 0x00000F00;
		this->gameObservers = flags & 0x40003000;
		this->teamTogether = flags & StartAdvExClient::TEAM_TOGETHER;
		this->fixedTeamLocations = flags & StartAdvExClient::TEAM_FIXED;
		this->sharedUnits = flags & StartAdvExClient::UNIT_SHARE;
		this->randomHeroes = flags & StartAdvExClient::HERO_RANDOM;
		this->randomRaces = flags & StartAdvExClient::RACE_RANDOM;
		
		ds.setByteOrder((QDataStream::ByteOrder)bo);
	}

	virtual void writeTo(QDataStream& ds)
	{
		int bo = ds.byteOrder();
		ds.setByteOrder(QDataStream::LittleEndian);

		/* Build game flags. */
		quint32 flags = 0;
		flags |= this->gameSpeed;
		flags |= this->gameVisibility;
		flags |= this->gameObservers;
		flags |= (this->teamTogether)? StartAdvExClient::TEAM_TOGETHER : 0;
		flags |= (this->fixedTeamLocations)? StartAdvExClient::TEAM_FIXED : 0;
		flags |= (this->sharedUnits)? StartAdvExClient::UNIT_SHARE : 0;
		flags |= (this->randomHeroes)? StartAdvExClient::HERO_RANDOM : 0;
		flags |= (this->randomRaces)? StartAdvExClient::RACE_RANDOM : 0;

		/* Build stat string. */
		QByteArray ss;
		QDataStream ss_stream(ss);
		ss_stream.setByteOrder(QDataStream::LittleEndian);
		ss_stream << (quint32)flags;
		ss_stream << (quint8)0;
		ss_stream << this->mapWidth;
		ss_stream << this->mapHeight;
		ss_stream << this->mapCRC;
		writeStringTo(ss_stream, this->mapPath.toAscii());
		writeStringTo(ss_stream, this->lobbyHost.toAscii());
		ss_stream << (quint8)0;
		ss = BncsMessage::encodeStatString(ss);

		/* Send packet. */
		ds << (quint32)this->lobbyVisibility;
		ds << this->lobbyUptime;
		ds << (quint32)this->gameType;
		ds << (quint32)0x000003FF; /* Unknown constant. */
		ds << (quint32)0x00000000; /* Custom game constant. */
		writeStringTo(ds, this->lobbyName.toUtf8());
		ds << (quint8)this->hasPassword;
		ds << (quint8)98; /* Slots free. */ //FIXME: This should be variable, but the value is so retarded to calculate.
		ds.writeRawData("\x31\x30\x30\x30\x30\x30\x30\x30", 8); /* Host counter. */ //FIXME: This should be variable.
		writeStringTo(ds, ss); /* Stat string. */

		ds.setByteOrder((QDataStream::ByteOrder)bo);
	}
};

}
}
}
}

#endif
